#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <iostream>

#define MYPORT "4950"    // the port users will be connecting to

#define MAXBUFLEN 100

using namespace std;

class Server{
	protected:
	    int sockfd;
	    struct addrinfo hints, *servinfo, *p;
	    int rv;
	    int numbytes;
	    struct sockaddr_storage their_addr;
	    char buf[MAXBUFLEN];
	    socklen_t addr_len;
	    char s[INET6_ADDRSTRLEN];
	private:
	
	
	public:
		Server(){
			bindSocket();
			
			while(1){
				printf("listener: waiting to recvfrom...\n");
				addr_len = sizeof their_addr;
		   		if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
		        	(struct sockaddr *)&their_addr, &addr_len)) == -1){
		        		perror("recvfrom");
		        		exit(1);
				}
			
				printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family, 
					get_in_addr((struct sockaddr *)&their_addr),s, sizeof s));
				printf("listener: packet is %d bytes long\n", numbytes);
				buf[numbytes] = '\0';
				printf("listener: packet contains \"%s\"\n", buf);
			
				//do checksum
				if(checksum(buf)){
					//if checksum succeeded, send ack
					if ((numbytes = sendto(sockfd, "ack", strlen("ack"), 0, (struct sockaddr *)&their_addr, addr_len)) == -1){
			        		perror("server: sendto");
			        		exit(1);
					} else {
						cout << "Sent ack." << endl;
					}
				}
			}
		}
		
		~Server(){
			close(sockfd);
		}
		
		int checksum(char* packet){
			//check packet...
			return 1;
		}
		
		// get sockaddr, IPv4 or IPv6:
		void *get_in_addr(struct sockaddr *sa) {
		    if (sa->sa_family == AF_INET) {
		        return &(((struct sockaddr_in*)sa)->sin_addr);
		    }
		    return &(((struct sockaddr_in6*)sa)->sin6_addr);
		}
		
		int bindSocket(){
			cout << "Binding socket..." << endl;
			memset(&hints, 0, sizeof hints);
		    hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
		    hints.ai_socktype = SOCK_DGRAM;
		    hints.ai_flags = AI_PASSIVE; // use my IP
			
		    if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
		        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		        return 1;
		    }
			
		    // loop through all the results and bind to the first we can
		    for(p = servinfo; p != NULL; p = p->ai_next) {
		        if ((sockfd = socket(p->ai_family, p->ai_socktype,
		                p->ai_protocol)) == -1) {
		            perror("listener: socket");
		            continue;
		        }
				
		        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
		            close(sockfd);
		            perror("listener: bind");
		            continue;
		        }
				
		        break;
		    }
			
		    if (p == NULL) {
		        fprintf(stderr, "listener: failed to bind socket\n");
		        return 2;
		    }
			
			freeaddrinfo(servinfo);
			cout << "Socket bound." << endl;
			return 0;
		}
};

int main(void) {
	Server();
	return 0;
}
